using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using NUnit.Framework;
using PropertyExpression.Common;
using PropertyExpression.ControllableQuery.TypedReader;

namespace PropertyExpression.ControllableQuery.Test
{
    [TestFixture]
    public partial class DemoTest
    {
        [Test]
        public void Test01()
        {
            foreach (var record in MaterializeReader(
                _ =>
                    {
                        var builder = new StringBuilder(
                            @"SELECT
    ModelPublication.PublicationId,
    ModelPublication.ModelCode,
    ModelPublication.ModelSequence
FROM
    ModelPublication
WHERE
    1 = 1");
                        if (_.ModelCode.HasValue) builder.Append(@"
    AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
                        if (_.ModelSequence.HasValue) builder.Append(@"
    AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
                        return builder.ToString().ToQuery(new
                            {
                                PublicationId = default(Guid),
                                ModelCode = default(NVarChar<L4000>),
                                ModelSequence = default(NVarChar<L4000>)
                            });
                    },
                context => new
                    {
                        ModelCode = "ModelCode1".IfNullOrEmptyToNothing().Select(_ => context.Param(_)),
                        ModelSequence = "ModelSequence1".IfNullOrEmptyToNothing().Select(_ => context.Param(_))
                    }))
            {
                Console.WriteLine(
                    "PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
                    record.PublicationId,
                    record.ModelCode,
                    record.ModelSequence);
            }
        }

        public IEnumerable<TResult> MaterializeReader<T, TResult>(
            Func<T, IQuery<TResult>> func, Func<QueryContext, T> arg)
        {
            return MainTest.ExecuteReader(context => Func.Invoke(func, arg(context))).Select(reader => reader.Materialize2());
        }

        [Test]
        public void Test1()
        {
            foreach (var modelPublicationInfo in MainTest.ExecuteReader(
                context => GetModelPublications1(
                    context.Param("ModelCode1"),
                    context.Param("ModelSequence1")))
                .Select(reader => reader.Materialize()))
            {
                Console.WriteLine(
                    "PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
                    modelPublicationInfo.PublicationId,
                    modelPublicationInfo.ModelCode,
                    modelPublicationInfo.ModelSequence
                    );
            }
        }

        private static IQuery<IModelPublicationInfo1> GetModelPublications1(IParam<string> modelCode, IParam<string> modelSequence)
        {
            return
(@"SELECT
    ModelPublication.PublicationId,
    ModelPublication.ModelCode,
    ModelPublication.ModelSequence
FROM
    ModelPublication
WHERE
    ModelPublication.ModelCode = " + modelCode + @"
    AND ModelPublication.ModelSequence = " + modelSequence).ToQuery<IModelPublicationInfo1>();
        }

        [Test]
        public void Test2()
        {
            foreach (var reader in MainTest.ExecuteReader(
                context => GetModelPublications2(
                    context.Param("ModelCode1"),
                    context.Param("ModelSequence1"))))
            {
                Console.WriteLine(
                    "PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
                    reader.Item1(),
                    reader.Item2(),
                    reader.Item3()
                    );
            }
        }

        private static IQuery<Tuple<Guid, NVarChar<L4000>, NVarChar<L4000>>> GetModelPublications2(IParam<string> modelCode, IParam<string> modelSequence)
        {
            return
(@"SELECT
    ModelPublication.PublicationId AS Item1,
    ModelPublication.ModelCode AS Item2,
    ModelPublication.ModelSequence AS Item3
FROM
    ModelPublication
WHERE
    ModelPublication.ModelCode = " + modelCode + @"
    AND ModelPublication.ModelSequence = " + modelSequence).ToQuery<Tuple<Guid, NVarChar<L4000>, NVarChar<L4000>>>();
        }

        [Test]
        public void Test3()
        {
            foreach (var modelPublicationInfo in MainTest.ExecuteReader(
                context => GetModelPublications3(
                    context.Param("ModelCode1"),
                    context.Param("ModelSequence1")))
                .Select(reader => new ModelPublicationInfo3(
                                      reader.PublicationId(),
                                      reader.ModelCode(),
                                      reader.ModelSequence())))
            {
                Console.WriteLine(
                    "PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
                    modelPublicationInfo.PublicationId,
                    modelPublicationInfo.ModelCode,
                    modelPublicationInfo.ModelSequence
                    );
            }
        }

        private static IQuery<ModelPublicationInfo3> GetModelPublications3(IParam<string> modelCode, IParam<string> modelSequence)
        {
            return
(@"SELECT
    ModelPublication.PublicationId,
    ModelPublication.ModelCode,
    ModelPublication.ModelSequence
FROM
    ModelPublication
WHERE
    ModelPublication.ModelCode = " + modelCode + @"
    AND ModelPublication.ModelSequence = " + modelSequence).ToQuery<ModelPublicationInfo3>();
        }
    }

    public interface IModelPublicationInfo1
    {
        Guid PublicationId { get; }
        NVarChar<L4000> ModelCode { get; }
        NVarChar<L4000> ModelSequence { get; }
    }

    public class ModelPublicationInfo3
    {
        private readonly Guid publicationId;
        private readonly NVarChar<L4000> modelCode;
        private readonly NVarChar<L4000> modelSequence;

        public ModelPublicationInfo3(Guid publicationId, NVarChar<L4000> modelCode, NVarChar<L4000> modelSequence)
        {
            this.publicationId = publicationId;
            this.modelCode = modelCode;
            this.modelSequence = modelSequence;
        }

        public Guid PublicationId
        {
            get { return publicationId; }
        }

        public NVarChar<L4000> ModelCode
        {
            get { return modelCode; }
        }

        public NVarChar<L4000> ModelSequence
        {
            get { return modelSequence; }
        }
    }

    public static class Cq
    {
        public static T Materialize2<T>(this IDataReader<T> reader)
        {
            return Qwe<T>.Get(reader.SqlDataReader);
        }

        private static class Qwe<T>
        {
            private static volatile Func<SqlDataReader, T> func;
            private static readonly object syncRoot = new object();

            public static T Get(SqlDataReader reader)
            {
                if (func == null)
                {
                    lock (syncRoot)
                    {
                        if (func == null)
                        {
                            func = DynamicMaterializer.Build<SqlDataReader, T>(
                                type =>
                                {
                                    if (type == typeof(string))
                                    {
                                        return typeof(Cq).GetMethod("GetString");
                                    }
                                    else if (type == typeof(int))
                                    {
                                        return typeof(Cq).GetMethod("GetInt");
                                    }
                                    else if (type == typeof(Guid))
                                    {
                                        return typeof(Cq).GetMethod("GetGuid");
                                    }
                                    else if (type == typeof(NVarChar<L4000>))
                                    {
                                        return typeof(Cq).GetMethod("NVarChar4000");
                                    }
                                    else if (type == typeof(DateTime))
                                    {
                                        return typeof(Cq).GetMethod("GetDateTime");
                                    }
                                    else if (type == typeof(int?))
                                    {
                                        return typeof(Cq).GetMethod("GetNullableInt");
                                    }
                                    else
                                    {
                                        throw new NotImplementedException();
                                    }
                                }, reader);
                        }
                    }
                }
                return func(reader);
            }
        }

        public static string GetString(SqlDataReader reader, int fieldIndex)
        {
            return reader.GetString(fieldIndex);
        }

        public static DateTime GetDateTime(SqlDataReader reader, int fieldIndex)
        {
            return reader.GetDateTime(fieldIndex);
        }

        public static int GetInt(SqlDataReader reader, int fieldIndex)
        {
            return reader.GetInt32(fieldIndex);
        }

        public static Guid GetGuid(SqlDataReader reader, int fieldIndex)
        {
            return reader.GetGuid(fieldIndex);
        }

        public static NVarChar<L4000> NVarChar4000(SqlDataReader reader, int fieldIndex)
        {
            return new NVarChar<L4000>(reader.GetString(fieldIndex));
        }

        public static int? GetNullableInt(SqlDataReader reader, int fieldIndex)
        {
            if (reader.IsDBNull(fieldIndex)) return null;
            return reader.GetInt32(fieldIndex);
        }
    }
}